iT邦幫忙

2024 iThome 鐵人賽

DAY 11
0

Seedwork 用途

還記得我們前面介紹的 DDD 三種物件規則嗎?不記得可以回去翻翻 Day 06 - Tactical Design 的文章。

我們這篇章要依照上述的規則來在 Common Library 內實作這些規則的介面,好讓所有 Microservices 都可以依照這些準則實作。

1. Entity 實作

Entity 具有唯一的 ID,其主要特徵是 ID 的不可變性,其他屬性則可以隨著業務邏輯而改變。只要兩個實體的 ID 相同,即使其他屬性不同,它們也被視為相同的物件。以下為 Entity 的實作:

namespace Common.Library.Seedwork;

public abstract class Entity<TId> where TId : notnull
{
    // 實體內部持有一個 Domain Event List,用來追蹤事件
    private readonly List<IDomainEvent> _domainEvents = new(); 

    // ID 是必需且不可變的
    public required TId Id { get; init; } 
    public DateTime CreatedDateTime { get; set; } 
    public DateTime UpdatedDateTime { get; set; } 

    // 取得 Domain Event List
    public IReadOnlyList<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();

    // 增加 Domain Event
    public void AddDomainEvent(IDomainEvent domainEvent)
    {
        _domainEvents.Add(domainEvent);
    }

    // 清除所有的 Domain Events
    public void ClearDomainEvents()
    {
        _domainEvents.Clear();
    }
}

Entity 實作重點

  • Id 是不可變的,實現了對唯一性的保證。
  • Domain Event 用於處理實體的業務邏輯變化,並支持事件驅動的模式。

2. Domain Event 實作

Domain Event 是一種用來通知外部發生了某些業務邏輯變更的機制。使用 MediatR 來處理事件的傳播:

using MediatR;

namespace Common.Library.Seedwork;

// 定義 Domain Event 接口,繼承自 INotification(MediatR 的接口)
public interface IDomainEvent : INotification
{
}

3. Value Object 實作

Value ObjectEntity 不同,它沒有唯一的 ID。它只關注其屬性的值,而不是身份。Value Object 一旦創建後是不可變的,任何改變都應該通過創建新實例來完成。以下是 Value Object 的實作:

namespace Common.Library.Seedwork;

// Value Object基類,強調不可變性
public abstract class ValueObject
{
    // 子類別需實作此方法,回傳物件的屬性集合,用於相等性判斷
    // 比如,若有 Address Value Object,它的屬性可能包括 City、Street、ZipCode 等
    protected abstract IEnumerable<object> GetEqualityComponents();

    // 覆寫 Equals 方法來比較兩個 ValueObject 是否相等
    // 使用 SequenceEqual 比較兩個物件的屬性集合是否完全一致
    public override bool Equals(object? obj)
    {
        // 若 obj 為 null 或者 obj 的型別與當前物件的型別不同,則視為不相等
        if (obj == null || obj.GetType() != GetType())
            return false;

        // 轉型為 ValueObject 並使用 GetEqualityComponents 來逐一比較屬性
        return GetEqualityComponents()
            .SequenceEqual(((ValueObject)obj).GetEqualityComponents());
    }

    // 覆寫 GetHashCode 方法來生成物件的 Hash Code
    // 如果兩個物件的所有屬性值完全相同,則它們的 Hash Code 也會相同
    // 透過 GetEqualityComponents 的每個屬性生成 Hash Code,並以 XOR 結合
    public override int GetHashCode()
    {
        return GetEqualityComponents()
            .Aggregate(0, (hash, component) => hash ^ (component?.GetHashCode() ?? 0));
    }

    // 定義 == 運算符,用來比較兩個 ValueObject 是否相等
    // 使用 ReferenceEquals 來判斷是否為同一個實例,若不是,則調用 Equals 方法進行比較
    public static bool operator ==(ValueObject left, ValueObject right)
    {
        if (ReferenceEquals(left, null) || ReferenceEquals(right, null))
            return ReferenceEquals(left, right);
        return left.Equals(right);
    }

    // 定義 != 運算符,當兩個物件不相等時返回 true,否則返回 false
    public static bool operator !=(ValueObject left, ValueObject right)
    {
        return !(left == right);
    }
}

Value Object 實作重點

  • Value Object 沒有 ID,價值由其屬性值決定。
  • 值比較是通過 Equals 方法,確保 Value Object 是不可變的。
  • 若兩個 Value Object 的所有屬性值相同,則它們被視為相同的物件。

4. Aggregate 與 Aggregate Root

Aggregate 是一個由多個 Entity 和/或 Value Object 組成的集合,它定義了內部的邊界上下文,所有操作都必須通過 Aggregate Root 來進行。

namespace Common.Library.Seedwork;

// 聚合根標示接口,所有聚合根都應實作此接口
public interface IAggregateRoot
{
}

Aggregate 實作重點

  • IAggregateRoot 接口標示了該實體是 Aggregate Root。
  • 所有的外部操作都通過 Aggregate Root 執行,以確保 Aggregate 內部的一致性。
  • 不同的 Aggregate 透過其 Aggregate Root 的 ID 進行交互,避免耦合問題。

5. Repository 與 Unit of Work 實作

因為 Aggregate Root 是一個特定的 Entity,負責管理整個 Aggregate 的一致性與完整性。所以我們必些要用一些 Patterns 來保證對所有的 Aggregates 的操作都會是一致且完整的。
故我們需要多實作 RepositoryUnit of Work 的介面。Repository 負責資料持久化,而 Unit of Work 則負責管理事務的一致性。

namespace Common.Library.Seedwork;

// 定義儲存庫接口,限制只適用於聚合根
public interface IRepository<T> where T : IAggregateRoot
{
    IUnitOfWork UnitOfWork { get; }
}

// 定義 Unit of Work 接口,用來管理事務操作
public interface IUnitOfWork : IDisposable
{
    Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default);
}

實作重點

  • Repository 將操作集中在聚合根上,避免操作具體的實體。
  • Unit of Work 確保事務的一致性,所有操作要麼全部成功,要麼全部 Rollback。

結語

這裡展示了 DDD 相關核心概念在實作上的具體方式,特別針對 EntityValue Object 以及 Aggregate 的處理。在 Common Library 設定好清晰的物件規則,我們可以保持系統的模組化,並促進良好的架構設計。

https://ithelp.ithome.com.tw/upload/images/20240924/20168953i9UAygV5mH.png


上一篇
Day 10 - 專案建置與 docker-compose
下一篇
Day 12 - 實作 Domain Aggregates
系列文
DDD? Clean Architecture? Microservices? 帶你用.NET實作打造一個現代化微服務!13
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言